<?php
/**
 * Eka Framework 0.3.0
 *
 * @copyright Copyright (c) 2008 Alexey Yu. Chuprina <656b61@gmail.com> (http://php-eka.googlecode.com)
 * @license http://www.opensource.org/licenses/mit-license.php MIT License
 *
 * @version $Id$
 */

/**
 * Eka_Db_Table_Abstract
 *
 * @package Db
 * @subpackage Table
 */
abstract class Eka_Db_Table_Abstract
{
	/**
	 * Adapter object
	 *
	 * @var Eka_Db_Adapter_Abstract
	 */
	protected $adapter;

	/**
	 * Database name
	 *
	 * @var string
	 */
	protected $tableSchema;

	/**
	 * Table name
	 *
	 * @var string
	 */
	protected $tableName;

	/**
	 * Table columns
	 *
	 * @var array
	 */
	protected $columns;
	
	/**
	 * Table column names
	 *
	 * @var array
	 */
	protected $columnNames;
	

	/**
	 * The primary key column or columns
	 *
	 * @var mixed
	 */
	protected $primary;

	/**
	 * Construct
	 *
	 * @param Eka_Db_Adapter_Abstract $adapter
	 * @param string $tableName
	 * @param string $tableSchema
	 */
	public function __construct(Eka_Db_Adapter_Abstract $adapter, $tableName, $tableSchema = null)
	{
		if (!$adapter instanceof Eka_Db_Adapter_Abstract) {
			require_once 'Eka/Db/Table/Exception.php';
			throw new Eka_Db_Table_Exception('Argument must be of type Eka_Db_Adapter_Abstract');
		}
		$this->adapter = $adapter;

		$this->tableName = $tableName;
		if (null !== $tableSchema) {
			$this->tableSchema = $tableSchema;
		} else if (strpos($tableName, '.')) {
			list($this->tableSchema, $this->tableName) = explode('.', $tableName);
		}

		if (null === $this->tableSchema) {
			$this->tableSchema = $this->adapter->getCurrentSchema();
			if (null === $this->tableSchema) {
				include_once 'Eka/Db/Table/Exception.php';
				throw new Eka_Db_Table_Exception('Database not defined');
			}
		}
	}

	/**
	 * Return Adapter
	 *
	 * @return Eka_Db_Adapter_Abstract
	 */
	final public function getAdapter()
	{
		return $this->adapter;
	}

	public function getMetadata()
	{
		return $this->adapter->describeTable($this->tableName, $this->tableSchema);
	}
	
	/**
	 * Return database name
	 *
	 * @return string
	 */
	public function getSchema()
	{
		return $this->tableSchema;
	}

	/**
	 * Return table name
	 *
	 * @return string
	 */
	public function getName()
	{
		return $this->tableName;
	}

	/**
	 * Return columns data
	 *
	 * @return array
	 */
	public function getColumns()
	{
		return $this->columns;
	}
	
	/**
	 * Return column names
	 *
	 * @return array
	 */
	public function getColumnNames()
	{
		return $this->columnNames;
	}

	/**
	 * Return primary column names
	 *
	 * @return array
	 */
	public function getPrimary()
	{
		return $this->primary;
	}

	/**
	 * Return db name and table name
	 *
	 * @return string
	 */
	public function getFullName()
	{
		return $this->tableSchema . '.' . $this->tableName;
	}
	
	/**
	 * @param array $data
	 * @param bool $new
	 */
	abstract protected function getRow(array $data = null, $new = false);

	/**
	 * Creare row
	 *
	 * @param array $data
	 * @return Eka_Db_Table_Row_Abstract
	 */
	public function createRow(array $data = null)
	{
		return $this->getRow($data, true);
	}

	/**
	 * Fetches one row in an object of type Eka_Db_Table_Row_Abstract
	 *
	 * @param mixed $where
	 * @param mixed $order
	 * @return Eka_Db_Table_Row_Abstract
	 */
	public function fetchOne($where = null, $order = null)
	{
		$stmt = $this->doFetch($where, $order, 1);
		if ($stmt->rowCount() == 0) {
			return null;
		}
		return $this->getRow($stmt->fetchAssoc());
	}

	/**
	 * Fetches rows
	 *
	 * @param mixed $where
	 * @param mixed $order
	 * @param int $count
	 * @param int $offset
	 * @return array
	 */
	public function fetch($where = null, $order = null, $count = null, $offset = null)
	{
		$stmt = $this->doFetch($where, $order, $count, $offset);
		$result = array();
		while (false !== ($row = $stmt->fetchAssoc())) {
			$result[] = $this->getRow($row);
		}
		return $result;
	}

	/**
	 * Find row
	 *
	 * @param mixed $value
	 * @return Eka_Db_Table_Row_Abstract
	 */
	public function getOne($value)
	{
		$stmt = $this->doGet($value, null, 1);
		if ($stmt->rowCount() == 0) {
			return null;
		}
		return $this->getRow($stmt->fetchAssoc());
	}
	
	/**
	 * Find
	 *
	 * @param array $value
	 * @param mixed $order
	 * @param int $count
	 * @param int $offset
	 * @return Eka_Db_Table_Rowset_Abstract
	 */
	public function get(array $value, $order = null, $count = null, $offset = null)
	{
		$stmt = $this->doGet($value, $order, $count, $offset);
		$result = array();
		while (false !== ($row = $stmt->fetchAssoc())) {
			$result[] = $this->getRow($row);
		}
		return $result;
	}

	/**
	 * @param midex $value
	 * @param midex $order
	 * @param int $count
	 * @param int $offset
	 * @return Eka_Db_Statement_Abstract
	 */
	protected function doGet($value, $order = null, $count = null, $offset = null)
	{
		if (!$this->primary) {
			include_once 'Eka/Db/Table/Exception.php';
			throw new Eka_Db_Table_Exception('You can not use this method, because the table has no primary key');
		}
		if (count($this->primary) == 1) {
			$where = reset($this->primary);
			if (is_array($value)) {
				$where .= ' IN (' . join(',', array_fill(0, count($value), '?')) . ')';
			} else {
				$where .= ' = ?';
			}
			$bind = $value;
		} else {
			$bind = array();
			$whereElm = array();
			foreach ($this->primary as $col) {
				$whereElm[] = $col .' = ?';
			}
			$whereElm = join(' AND ', $whereElm);
			$where = array();
			if (!is_array(reset($value))) {
				$value = array($value);
			}
			foreach ($value as $value) {
				if (is_array($value)) {
					$bind = array_merge($bind, $value);
				} else {
					$bind[] = $value;
				}
				$where[] = $whereElm;
			}
			$where = join(' OR ', $where);
		}
		$where = array($where => $bind);
		return $this->doFetch($where, $order, $count, $offset);
	}

	/**
	 * Support method for fetching rows
	 *
	 * @param midex $where
	 * @param midex $order
	 * @param int $count
	 * @param int $offset
	 * @return Eka_Db_Statement_Abstract
	 */
	protected function doFetch($where = null, $order = null, $count = null, $offset = null)
	{
		return $this->select($this->columnNames, $where, $order, $count, $offset);
	}
	
	/**
	 * Select a table rows
	 *
	 * @param mixed $columns
	 * @param mixed $where
	 * @param mixed $order
	 * @param int $count
	 * @param int $offset
	 * @return Eka_Db_Statement_Abstract
	 */
	public function select($columns = null, $where = null, $order = null, $count = null, $offset = null)
	{
		return $this->adapter->select($this->getFullName(), $columns, $where, $order, $count, $offset);
	}

	/**
	 * Inserts a table row
	 *
	 * @param array $data Array of column => value
	 * @return int Last insert id, if possible
	 */
	public function insert($data)
	{
		return $this->adapter->insert($this->getFullName(), $data);
	}

	/**
	 * Updates table rows
	 *
	 * @param array $data Array of column => value
	 * @param mixed $where
	 * @param mixed $order
	 * @param int $count
	 * @param int $offset
	 * @return int The number of affected rows
	 */
	public function update($data, $where = null, $order = null, $count = null, $offset = null)
	{
		return $this->adapter->update($this->getFullName(), $data, $where, $order, $count, $offset);
	}

	/**
	 * Deletes table rows
	 *
	 * @param mixed $where
	 * @param mixed $order
	 * @param int $count
	 * @param int $offset
	 * @return int Number of affected rows
	 */
	public function delete($where = null, $order = null, $count = null, $offset = null)
	{
		return $this->adapter->delete($this->getFullName(), $where, $order, $count, $offset);
	}
}